﻿using System;
using CoreTypes;
using System.Xml;
using System.Linq;
using System.Text;
using System.Custom;
using System.Xml.Linq;
using System.Threading;
using csammisrun.OscarLib;
using System.Custom.Window;
using System.Collections.Generic;
using System.Globalization;

namespace OscarServiceTransport
{
   public class CommunicationServiceObject : CommunicationServiceObjectBase
   {
      #region Default Network Settings

      public const string DefaultNetworkSettings =

@"<AolServiceTransport>

      <Service Name='AIM'
         Culture='US EN'
         ProtocolName='OSCAR'
         ProtocolVersion='9'
         LoginPrefix='AIM'
         LoginAlias='AOL Screenname'
         HomePage='http://www.aol.com/'
         RegisterUrl='http://www.aol.com/'
         PasswordUrl='http://www.aol.com/'
         PersonalUrl='http://my.aol.com/' >
         <Configuration UserDefined='false' ReadOnly='false'>
            <Supports Features='AllowList BlockList AddContact AddGroup RemoveGroup FindPerson'
                      SearchBy='Id'
                      Actions=''
                      GettingOf='Nickname FullName FirstName LastName Email Country City'
                      SettingOf='' />
         </Configuration>
      </Service>
     
      <Service Name='ICQ'
         Culture='RU DE CS he-IL'
         ProtocolName='OSCAR'
         ProtocolVersion='9'
         LoginPrefix='ICQ'
         LoginCaption='ICQ'
         HomePage='http://www.icq.com/'
         RegisterUrl='http://www.icq.com/register/'
         PasswordUrl='http://www.icq.com/password/'
         PersonalUrl='http://www.icq.com/people/about_admin.php' >
         <Configuration UserDefined='false' ReadOnly='false'>
            <Supports Features='AllowList BlockList AddContact AddGroup RemoveGroup FindPerson'
                      SearchBy='Id'
                      Actions=''
                      GettingOf='Nickname FullName FirstName LastName Email Country City'
                      SettingOf='' />
         </Configuration>
      </Service>

  </AolServiceTransport>";

      #endregion

      #region CommunicationServiceObject Data

      static OscarDriverSettings FDriverSettings;
      
      internal const ushort AolLoginServerPort = 5190;//443
      internal const string AolLoginServerHost = "login.oscar.aol.com";//"kdc.uas.aol.com"

      bool FWriteLog = false;
      string FDeliveryReportPrefix = "AOL message delivery report: ";
      Dictionary<Account, Session> FAolSessions = new Dictionary<Account, Session>();
      //Dictionary<string, csammisrun.OscarLib.UserInfo> FBuddyCache = new Dictionary<string, csammisrun.OscarLib.UserInfo>();

      static int _MoveUserCount = 0;
      static bool _RemoveUserOccured = false;

      #endregion

      #region CommunicationServiceObject Initializers

      public CommunicationServiceObject(string assemblyName, IBaseCommunicator owner)
         : base(assemblyName, owner)
      {
         FDriverErrorPrefix = "AOL driver error: ";
      }

      #endregion

      #region CommunicationServiceObject Members

      private Account GetAccount(User user)
      {
         Account account = user is Account ? user as Account :
            (user is Contact ? (user as Contact).Owner : FCommunicator.CurrentAccount);
         return account;
      }
      private bool IsValidUIN(string uinStr)
      {
         if (string.IsNullOrEmpty(uinStr)) return false;

         uint uin = 0;
         uinStr = string.Concat(uinStr.Split('-')).TrimIgnoreNull();
         return uint.TryParse(uinStr, out uin);
      }
      private XmlDocument CreateXmlContacts()
      {
         XmlDocument doc = new XmlDocument();

         doc.InnerXml = "<!DOCTYPE Contacts [" +
                       "<!ELEMENT Contacts ANY>" +
                       "<!ELEMENT Group ANY>" +
                       "<!ELEMENT Contact ANY>" +
                       "<!ATTLIST Group Id ID #REQUIRED>" +
                       "<!ATTLIST Contact Identifier ID #REQUIRED>]><Contacts />";
         return doc;
      }
      private bool IsIcqAccount(Account account)
      {
         return account.ServiceConfig.Service.ServiceName == "ICQ";
      }
      private bool IsAimAccount(Account account)
      {
         return !IsIcqAccount(account);
      }
      private Session CreateAolSession(Account account)
      {
         Session session = new Session(account.Login,
                              IsIcqAccount(account) ? account.Password.Substr(0, 8) : account.Password);

         if (FWriteLog) session.InitializeLogger(Environment.CurrentDirectory);

         session.ClientCapabilities = Capabilities.Chat | Capabilities.SendFiles | Capabilities.OscarLib | Capabilities.DirectIM;
         session.LoginCompleted += new LoginCompletedHandler(session_LoginCompleted);
         session.ErrorMessage += new ErrorMessageHandler(session_ErrorMessage);
         session.WarningMessage += new WarningMessageHandler(session_WarningMessage);
         session.StatusUpdate += new InformationMessageHandler(session_StatusUpdate);
         session.LoginFailed += new LoginFailedHandler(session_LoginFailed);
         session.LoginStatusUpdate += new LoginStatusUpdateHandler(session_LoginStatusUpdate);
         session.DirectoryUpdateAcknowledged += new DirectoryUpdateAcknowledgedHandler(session_DirectoryUpdateAcknowledged);

         session.Statuses.UserStatusReceived += new UserStatusReceivedHandler(session_UserStatusReceived);
         session.Statuses.UserInfoReceived += new UserInfoReceivedHandler(session_UserInfoReceived);
         session.Statuses.UserOffline += new UserOfflineHandler(session_UserStatusReceived);

         session.Searches.FindByEmailResults += new FindByEmailResultsHandler(session_FindByEmailResults);
         session.ICQ.SearchResultReceived += new EventHandler<SearchResultReceivedEventArgs>(session_SearchResultReceived);
         session.ICQ.UserInfoResponseReceived += new EventHandler<SearchResultReceivedEventArgs>(session_UserInfoResponseReceived);

         session.GroupItemReceived += new GroupItemReceivedHandler(session_GroupItemReceived);
         session.BuddyItemReceived += new BuddyItemReceivedHandler(session_BuddyItemReceived);
         session.ContactListFinished += new ContactListFinishedHandler(session_ContactListFinished);
         session.SSIEditComplete += new SSIEditCompleteHandler(session_SSIEditComplete);

         session.ChatInvitationReceived += new ChatInvitationReceivedHandler(session_ChatInvitationReceived);
         session.ChatRooms.ChatRoomJoined += new ChatRoomJoinedHandler(session_ChatRoomCreated);
         session.ChatRooms.ChatRoomCreationFailed += new ChatRoomErrorHandler(session_ChatRoomCreationFailed);

         session.DirectIMRequestReceived += new DirectIMRequestReceivedHandler(session_DirectIMRequestReceived);
         session.DirectIMReceived += new DirectIMReceivedHandler(session_DirectIMReceived);

         session.Messages.MessageReceived += new MessageReceivedHandler(session_MessageReceived);
         session.Messages.BadMessageReceived += new EventHandler<MessageManager.BadMessageReceivedEventArgs>(session_BadMessageReceived);
         session.Messages.TypingNotification += new TypingNotificationEventHandler(session_TypingNotification);
         session.Messages.OfflineMessagesReceived += new OfflineMessagesReceivedEventHandler(session_OfflineMessagesReceived);
         session.Messages.MessageDeliveryUpdate += new MessageDeliveryUpdateEventHandler(session_MessageDeliveryUpdate);
         session.AuthorizationRequestReceived += new AuthorizationRequestReceivedHandler(session_AuthorizationRequestReceived);
         session.AuthorizationResponseReceived += new AuthorizationResponseReceivedHandler(session_AuthorizationResponseReceived);
         session.FutureAuthorizationReceived += new FutureAuthorizationReceivedHandler(session_FutureAuthorizationReceived);

         //session.Graphics.AutoSaveLocation = Environment.CurrentDirectory;
         session.Graphics.BuddyIconReceived += new BuddyIconReceivedHandler(graphics_BuddyIconReceived);
         session.Graphics.BuddyIconDownloaded += new BuddyIconDownloadedHandler(graphics_BuddyIconDownloaded);
         session.Graphics.BuddyIconUploadCompleted += new BuddyIconUploadCompletedHandler(graphics_BuddyIconUploadCompleted);
         session.Graphics.BuddyIconUploadFailed += new BuddyIconUploadFailedHandler(graphics_BuddyIconUploadFailed);

         //client.FileTransferProgress += new FileTransferProgressHandler(session_FileTransferProgress);
         //client.FileTransferCompleted += new FileTransferCompletedHandler(session_FileTransferCompleted);
         //client.FileTransferRequestReceived += new FileTransferRequestReceivedHandler(session_FileTransferRequestReceived);

         return session;
      }
      private Account GetAccountBySession(Session session)
      {
         foreach (var item in FAolSessions) if (item.Value == session) return item.Key;

         return null;
      }
      private Session GetAolSession(Account account, bool create)
      {
         Session session = null;

         FAolSessions.TryGetValue(account, out session);

         return session != null ? session : (create ? CreateAolSession(account) : null);
      }
      private SSIBuddy AddBuddy(Session session, Contact contact)
      {
         SSIGroup group = session.SSI.GetGroupByName(contact.Group.Name);
         
         if (group == null) group = session.SSI.GetGroupByName("");

         session.AddBuddy(contact.Login, group.ID, contact.CustomName, contact.Info.Email, "", "", "", true, "");

         SSIBuddy buddy = GetBuddyByName(session, contact.Login);

         buddy.AwaitingAuthorization = true;

         return buddy;
      }
      private SSIBuddy GetBuddyByName(Session session, string name)
      {
         return session.SSI.GetSSIBuddyList().Find(b => b.Name == name);
      }
      private Session GetSessionByManager(ISnacFamilyHandler manager)
      {
         Session session = null;

         foreach (var p in FAolSessions)
         {
            if (p.Value.Statuses == manager ||
                p.Value.Messages == manager ||
                p.Value.ICQ == manager)
            {
               session = p.Value;
               break;
            }
         }

         return session;
      }
      private SSIDeny GetDenyItemByName(Session session, string name)
      {
         IEnumerable<SSIDeny> denies = session.SSI.GetSSIItems<SSIDeny>();
         foreach (SSIDeny item in denies) if (item.Name == name) return item;
         return null;
      }
      private SSIPermit GetPermitItemByName(Session session, string name)
      {
         IEnumerable<SSIPermit> permits = session.SSI.GetSSIItems<SSIPermit>();
         foreach (SSIPermit item in permits) if (item.Name == name) return item;
         return null;
      }
      private ServiceOperationException GetOperationFault(Exception error)
      {
         if (error == null) return null;

         return new ServiceOperationException(FDriverErrorPrefix, error);
      }
      private ServiceOperationException GetOperationFault(MessageStatus reason)
      {
         ServiceOperationException error = null;

         switch (reason)
         {
            case MessageStatus.UnknownError:
            {
               error = new ServiceOperationException(FDeliveryReportPrefix + "unknown error"); break;
            }
            case MessageStatus.BuddyOffline:
            {
               error = new ServiceOperationException(FDeliveryReportPrefix + "buddy is offline"); break;
            }
            case MessageStatus.OfflineMessagesNotSupported:
            {
               error = new ServiceOperationException(FDeliveryReportPrefix + "offline messages not supported"); break;
            }
            case MessageStatus.OfflineStorageFull:
            {
               error = new ServiceOperationException(FDeliveryReportPrefix + "offline storage full"); break;
            }
            case MessageStatus.MessageTimeout:
            {
               error = new ServiceOperationException(FDeliveryReportPrefix + "message timeout"); break;
            }
         }
         return error;
      }
      private ServiceOperationException GetOperationFault(LoginErrorCode reason)
      {
         ServiceOperationException error = null;

         switch (reason)
         {
            case LoginErrorCode.WrongPassword:
            case LoginErrorCode.AccountExpired:
            case LoginErrorCode.AccountDeleted:
            case LoginErrorCode.AccountInvalid:
            case LoginErrorCode.BadAuthorizationInfo:
            case LoginErrorCode.InvalidScreennamePassword:
            case LoginErrorCode.IncorrectScreennamePassword:
            {
               error = new ServiceOperationException(ErrorReasonCode.Credentials, FDriverErrorPrefix + "Invalid login or password."); break;
            }
            case LoginErrorCode.ServiceUnavailable:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServiceFailure, FDriverErrorPrefix + "Service is unavailable."); break;
            }
            case LoginErrorCode.DatabaseNoAccess:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Database is not accessible."); break;
            }
            case LoginErrorCode.ResolverNoAccess:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Resolver is not accessible."); break;
            }
            case LoginErrorCode.DatabaseInvalidFields:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Database invalid fields."); break;
            }
            case LoginErrorCode.DatabaseBadStatus:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Database bad status."); break;
            }
            case LoginErrorCode.ResolverBadStatus:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Resolver bad status."); break;
            }
            case LoginErrorCode.InternalError:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Internal error."); break;
            }
            case LoginErrorCode.ServiceOffline:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServiceFailure, FDriverErrorPrefix + "Service is offline."); break;
            }
            case LoginErrorCode.AccountSuspended:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Account is suspended."); break;
            }
            case LoginErrorCode.DatabaseSendError:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Database send error."); break;
            }
            case LoginErrorCode.DatabaseLinkError:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Database link error."); break;
            }
            case LoginErrorCode.ReservationMapError:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Reservation map error."); break;
            }
            case LoginErrorCode.ReservationLinkError:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Reservation link error."); break;
            }
            case LoginErrorCode.ReservationTimeout:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Reservation timeout."); break;
            }
            case LoginErrorCode.TooManyUsersFromIP:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Too many users from IP."); break;
            }
            case LoginErrorCode.RateLimitExceeded:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Rate limit exceeded. Try later."); break;
            }
            case LoginErrorCode.WarningLevelTooHigh:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Warning level too high."); break;
            }
            case LoginErrorCode.OldICQVersion:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Old client version."); break;
            }
            case LoginErrorCode.CantRegisterICQ:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Cant register account."); break;
            }
            case LoginErrorCode.InvalidSecurID:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Invalid secure ID."); break;
            }
            case LoginErrorCode.AccountSuspendedBecauseOfAge:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Account was suspended because of age."); break;
            }
            case LoginErrorCode.CantReachAuthServer:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServiceFailure, FDriverErrorPrefix + "Authentification server is unreachable."); break;
            }
            case LoginErrorCode.CantReachBOSServer:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServiceFailure, FDriverErrorPrefix + "BOS server is unreachable."); break;
            }
            case LoginErrorCode.UnknownError:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServiceFailure, FDriverErrorPrefix + "Unknown error."); break;
            }
            default:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServiceFailure, FDriverErrorPrefix + reason.ToString()); break;
            }
         }
         return error;
      }
      private ServiceOperationException GetOperationFault(ServerErrorCode reason)
      {
         ServiceOperationException error = null;

         switch (reason)
         {
            case ServerErrorCode.InvalidSNACHeader:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Invalid SNAC header."); break;
            }
            case ServerErrorCode.ServerRateLimited:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Server rate limited."); break;
            }
            case ServerErrorCode.ClientRateLimited:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Client rate limited."); break;
            }
            case ServerErrorCode.ReceiverNotLoggedIn:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Receiver not logged in."); break;
            }
            case ServerErrorCode.RequestedServiceUnavailable:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Requested service is unavailable."); break;
            }
            case ServerErrorCode.RequestedServiceUnknown:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Requested service is unknown."); break;
            }
            case ServerErrorCode.ObsoleteSNAC:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Obsolete SNAC."); break;
            }
            case ServerErrorCode.NotSupportedByServer:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Not supported by the server."); break;
            }
            case ServerErrorCode.NotSupportedByClient:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Not supported by client."); break;
            }
            case ServerErrorCode.RefusedByClient:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Refused by client."); break;
            }
            case ServerErrorCode.ReplyTooBig:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Reply too big."); break;
            }
            case ServerErrorCode.ResponsesLost:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Responses lost."); break;
            }
            case ServerErrorCode.RequestDenied:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Request denied."); break;
            }
            case ServerErrorCode.InvalidSNACFormat:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Invalid SNAC format."); break;
            }
            case ServerErrorCode.InsufficientRights:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Insufficient rights."); break;
            }
            case ServerErrorCode.ReceiverBlocked:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Receiver blocked."); break;
            }
            case ServerErrorCode.SenderWarningTooHigh:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Sender warning too high."); break;
            }
            case ServerErrorCode.ReceiverWarningTooHigh:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Receiver warning too high."); break;
            }
            case ServerErrorCode.UserUnavailable:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "User unavailable."); break;
            }
            case ServerErrorCode.NoMatch:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "No match."); break;
            }
            case ServerErrorCode.ListOverflow:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "List overflow."); break;
            }
            case ServerErrorCode.AmbiguousRequest:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Ambiguous request."); break;
            }
            case ServerErrorCode.ServerQueueFull:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Server queue full."); break;
            }
            case ServerErrorCode.NotOnAOL:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Not on AOL."); break;
            }
            case ServerErrorCode.LostSecondaryConnection:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServiceFailure, FDriverErrorPrefix + "Secondary connection to the AOL server was lost."); break;
            }
            case ServerErrorCode.LostBOSConnection:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServiceFailure, FDriverErrorPrefix + "Primary connection to the AOL server was lost."); break;
            }
            case ServerErrorCode.UnrequestedChatRoomInformation:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Unrequested chat room information."); break;
            }
            case ServerErrorCode.OscarLibUnsupportedFunction:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "This feature is not supported by this driver."); break;
            }
            case ServerErrorCode.UnknownSNACFamily:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Unknown SNAC family."); break;
            }
            case ServerErrorCode.UnknownMessageChannel:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Unknown message channel."); break;
            }
            case ServerErrorCode.UnknownRendezvousChannel:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Unknown rendezvous channel."); break;
            }
            case ServerErrorCode.UnknownError:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "Unknown server error."); break;
            }
            case ServerErrorCode.SSIItemNotFound:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "SSI: item not found."); break;
            }
            case ServerErrorCode.SSIItemAlreadyExists:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "SSI: item already exists."); break;
            }
            case ServerErrorCode.SSICantAddItem:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "SSI: cant add item."); break;
            }
            case ServerErrorCode.SSIItemLimitExceeded:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "SSI: item limit exceeded."); break;
            }
            case ServerErrorCode.SSIItemRequiresAuthorization:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + "SSI: item requires authorization."); break;
            }
            default:
            {
               error = new ServiceOperationException(ErrorReasonCode.ServerInternal, FDriverErrorPrefix + reason.ToString()); break;
            }
         }
         return error;
      }
     
      #endregion

      #region ICommunicationServiceObject Members

      public override void Logoff(Account account)
      {
         Session session = GetAolSession(account, false);

         if (session != null) session.Logoff();

         NetworkOperation nwo = GetOperationByName(account.Login + ".LOGON");

         if (nwo != null)
         {
            OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Cancelled, null));
         }
         else
         {
            nwo = CreateOperation(account.Login + ".LOGOFF", true, account, 0, null, "Status", Status.Offline);

            OnNotifyPresenceState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSent, null));
         }

         RemoveOperation(nwo);
      }
      public override void DropAccount(Account account)
      {
         throw new NotSupportedException("Drop Account");
      }
      public override void SyncContacts(Account account)
      {
         //throw new NotSupportedException("Sync Contacts");
      }
      public override void LoadAccountData(Account account)
      {
         RequestUserInfo(account, false);
      }
      public override void CreateAccount(RegistrationInfo info)
      {
         throw new NotSupportedException("Create Account");
      }
      public override void SendChatMessage(ChatMessage message)
      {
         Account account = message.Sender as Account;
         Session session = GetAolSession(account, false);

         if (session == null || !session.LoggedIn)
         {
            OnSendChatMessageState(this, new MessagingOperationEventArgs(message, null, null, OperationState.None, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         string nwoName = account.Login + "->" + message.Recipient.Login + ".CHATMESSAGE";

         NetworkOperation nwo = CreateOperation(nwoName, true, account, FLongTimeout, null, "Message", message);

         if (nwo == null)
         {
            OnSendChatMessageState(this, new MessagingOperationEventArgs(message, GetOperationByName(nwoName), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready)));
            return;
         }

         message.TsLocal = DateTime.UtcNow;

         OnSendChatMessageState(this, new MessagingOperationEventArgs(message, null, nwo, OperationState.DataSending, null));

         SSIBuddy buddy = GetBuddyByName(session, message.Recipient.Login);

         if (buddy == null) buddy = AddBuddy(session, (Contact)message.Recipient);

         if (buddy.AwaitingAuthorization && string.IsNullOrEmpty(buddy.AuthorizationReason))
         {
            nwo.Timeout = 1;
            message.NoAcknowledges = true;
            buddy.AuthorizationReason = message.Text;
            session.SendAuthorizationRequest(buddy.Name, message.Text);
            session.SendFutureAuthorizationGrant(buddy.Name, "You was added to my contacts.");
         }

         session.Messages.SendMessage(buddy.Name, message.Text, OutgoingMessageFlags.DeliverOffline);

         OnSendChatMessageState(this, new MessagingOperationEventArgs(message, null, nwo, OperationState.DataSent, null));
      }
      public override void UpdateMessage(ServiceMessage message)
      {
         throw new NotSupportedException("Update Message");
      }
      public override void Logon(Account account, Status status)
      {
         NetworkOperation nwo = CreateOperation(account.Login + ".LOGON", true, account, FLogonTimeout, null, "XContacts", CreateXmlContacts(), "Status", status);

         if (nwo == null)
         {
            OnLogonState(this, new NetworkOperationEventArgs(GetOperationByName(account.Login + ".LOGON"), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready)));
            return;
         }

         OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Initialization, null));

         if (nwo.State == OperationState.Cancelled)
         {
            OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Cancelled, null));
            RemoveOperation(nwo);
            return;
         }

         Session session = GetAolSession(account, true);

         if (session.LoggedIn)
         {
            OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Faulted, new ServiceOperationException(ErrorReasonCode.LoggedInAlready)));
            RemoveOperation(nwo);
            return;
         }

         #region /*Commented code*/

         //if (MainModule.Properties.Settings.Default.ProxyType.ToLower() == "none")
         //{
         //   session.ProxySetting = csammisrun.OscarLib.Utility.ProxyType.None;
         //}
         //else if (MainModule.Properties.Settings.Default.ProxyType.ToLower() == "socks")
         //{
         //   session.ProxySetting = csammisrun.OscarLib.Utility.ProxyType.Socks5;
         //}
         //else
         //{
         //   OnLogonState(this, new NetworkOperationEventArgs(null, OperationState.None, new ServiceOperationException("This type of proxy (" +
         //                MainModule.Properties.Settings.Default.ProxyType + ") is not supported by the currently installed ICQ driver.")));
         //   return;
         //}

         //session.ProxyServer = MainModule.Properties.Settings.Default.ProxyHost;
         //session.ProxyUsername = MainModule.Properties.Settings.Default.ProxyUsername;
         //session.ProxyPassword = MainModule.Properties.Settings.Default.ProxyPassword;

         //int port = 0;
         //int.TryParse(MainModule.Properties.Settings.Default.ProxyPort, out port);
         //session.ProxyPort = port;

         #endregion

         OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSending, null));

         session.Services.SetAvailableMessage(status.Message);
         session.Logon(account.ServiceConfig.Host, (int)account.ServiceConfig.Port);
      }
      public override void SeekUsers(string query, IWindow sender)
      {
         Session session = GetAolSession(FCommunicator.CurrentAccount, false);

         if (session == null || !session.LoggedIn)
         {
            OnSeekUsersState(this, new NetworkOperationEventArgs(null, OperationState.None, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         string uinStr = string.Concat(query.Split('-')).TrimIgnoreNull();

         if (IsIcqAccount(FCommunicator.CurrentAccount) && IsValidUIN(uinStr))
         {
            User targetUser = new User(FCommunicator.CurrentAccount.LoginPrefix + ":" + uinStr);

            NetworkOperation nwo = CreateOperation("REQUESTUSERINFO", true, Communicator.CurrentAccount, FLongTimeout, null, "User", targetUser, "IsSearch", true);

            if (nwo == null)
            {
               OnSeekUsersState(this, new NetworkOperationEventArgs(GetOperationByName("REQUESTUSERINFO"), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready)));
               return;
            }

            nwo.Results["Count"] = 0;

            nwo.NetworkOperationCallback = OnSeekUsersState;

            OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSending, null));

            session.ICQ.RequestUserInfo(targetUser.Login, false);
         }
         else if (CoreTypes.UserInfo.IsValidEmail(query))
         {
            NetworkOperation nwo = CreateOperation(FCommunicator.CurrentAccount.Login + ".FINDBYEMAIL", true, Communicator.CurrentAccount, FLongTimeout, null);

            if (nwo == null)
            {
               OnSeekUsersState(this, new NetworkOperationEventArgs(GetOperationByName(FCommunicator.CurrentAccount.Login + ".FINDBYEMAIL"), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready)));
               return;
            }

            nwo.NetworkOperationCallback = OnSeekUsersState;

            OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSending, null));

            session.Searches.FindUsersByEmail(query);
         }
         else
         {
            OnSeekUsersState(this, new NetworkOperationEventArgs(null, OperationState.None, new ServiceOperationException("Current version of the AOL driver does not support searches other then by E-mail or UIN (for ICQ accounts).")));
         }
      }
      public override void SendAcknowledge(ServiceMessage message)
      {
         throw new NotSupportedException("Send Acknowledge");
      }
      public override void SendInvitation(SessionInvitation message)
      {
         //throw new NotSupportedException("Send Invitation");
      }
      public override void RequestUserInfo(User user, bool shortInfo)
      {
         Account account = GetAccount(user);

         if (!IsIcqAccount(account))
         {
            OnRequestUserInfoState(this, new NetworkOperationEventArgs(null, OperationState.None, GetOperationFault(ErrorReasonCode.NotSupported, "", "AIM info")));
            return;
         }

         Session session = GetAolSession(account, false);

         if (session == null || !session.LoggedIn)
         {
            OnRequestUserInfoState(this, new NetworkOperationEventArgs(null, OperationState.None, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         NetworkOperation nwo = null;

         if (shortInfo)
         {
            // Callback is in the SearchResultReceived event

            nwo = CreateOperation(user.Login + ".REQUESTUSERINFO", true, account, FTimeout, null, "User", user);

            if (nwo == null)
            {
               OnRequestUserInfoState(this, new NetworkOperationEventArgs(GetOperationByName(user.Login + ".REQUESTUSERINFO"), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready)));
               return;
            }
         }
         else
         {
            // Callback is in the UserInfoResponseReceived event

            nwo = CreateOperation("REQUESTUSERINFO", true, account, FTimeout, null, "User", user);

            if (nwo == null)
            {
               OnRequestUserInfoState(this, new NetworkOperationEventArgs(GetOperationByName("REQUESTUSERINFO"), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready)));
               return;
            }

            nwo.Results["Count"] = 0;
         }

         nwo.NetworkOperationCallback = OnRequestUserInfoState;

         OnRequestUserInfoState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSending, null));

         session.ICQ.RequestUserInfo(user.Login, shortInfo);

         //if (user.Info.Userpic == null && FBuddyCache.ContainsKey(user.Login))
         //{
         //   session.Graphics.DownloadBuddyIcon(user.Login, FBuddyCache[user.Login].Icon);
         //}
      }
      public override void SendInvitationReply(ServiceMessage message)
      {
         throw new NotSupportedException("Send Invitation Reply");
      }
      public override void NotifyPresence(Account account, User user, Status status)
      {
         if (status.Value == StatusCodes.Offline)
         {
            Logoff(account);
            return;
         }

         Session session = GetAolSession(account, false);

         if (session == null || !session.LoggedIn)
         {
            Logon(account, status);
            return;
         }

         string nwoName = session.ScreenName + ".NOTIFYPRESENCE";

         NetworkOperation nwo = CreateOperation(nwoName, true, account, FTimeout, null, "Status", status);

         if (nwo == null)
         {
            OnNotifyPresenceState(this, new NetworkOperationEventArgs(GetOperationByName(nwoName), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready)));
            return;
         }

         if (status.IsAway()) session.Statuses.SetAwayMessage(status.Message);

         session.Services.SetExtendedICQStatus(ICQFlags.WebAware, TypeMap.MarshalStatus(status.Value));

         if (status.Value == StatusCodes.Invisible) session.SSI.Privacy = PrivacySetting.AllowOnlyPermitList;
         else if (status.Value == StatusCodes.Phantom) session.SSI.Privacy = PrivacySetting.BlockAllUsers;
         else session.SSI.Privacy = PrivacySetting.BlockOnlyDenyList;

         OnNotifyPresenceState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSent, null));

         RemoveOperation(nwo);
      }
      public override void ChangePrivateData(Account account, string password, string mail)
      {
         throw new NotSupportedException("Change Private Data");
      }
      public override void SaveAccountData(Account account, SaveAccountActions actions, params object[] data)
      {
         if (actions == SaveAccountActions.None) return;

         Session session = GetAolSession(account, false);

         if (session == null || !session.LoggedIn)
         {
            OnSaveAccountDataState(this, new SaveAccountDataEventArgs(actions, null, OperationState.Completed, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         bool handled = false;

         if ((actions & SaveAccountActions.SaveInfo) == SaveAccountActions.SaveInfo)
         {
            //handled = true;

            //NetworkOperation nwo = CreateOperation(session.ScreenName + ".SAVEACCOUNTDATA", true, account, FTimeout, null, "Actions", actions);

            //SNAC02.SetDirectoryInformation(session, true, account.Info.FullName.SubstrBefore(" "), "",
            //         account.Info.FullName.Contains(" ") ? account.Info.FullName.SubstrAfter(" ") : "", "",
            //         account.Nickname, account.Info.City, "", account.Info.Country, "", "");

            //OnSaveAccountDataState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSent, null));
         }

         if ((actions & SaveAccountActions.AddContact) == SaveAccountActions.AddContact)
         {
            handled = true;

            if (data.Length < 1) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.AddContact.ToString());

            AddBuddy(session, (Contact)data[0]);
         }
         else if ((actions & SaveAccountActions.AddGroup) == SaveAccountActions.AddGroup)
         {
            handled = true;

            if (data.Length < 1) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.AddGroup.ToString());

            session.AddGroup((string)data[0]);
         }
         else if ((actions & SaveAccountActions.RemoveContact) == SaveAccountActions.RemoveContact)
         {
            handled = true;

            if (data.Length < 1) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.RemoveContact.ToString());

            Contact contact = (Contact)data[0];
            SSIBuddy buddy = GetBuddyByName(session, contact.Login);

            if (buddy != null) session.RemoveBuddy(buddy.ItemID, buddy.GroupID);

            _RemoveUserOccured = true;
         }
         else if ((actions & SaveAccountActions.RemoveGroup) == SaveAccountActions.RemoveGroup)
         {
            handled = true;

            if (data.Length < 1) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.RemoveGroup.ToString());

            SSIGroup group = session.SSI.GetGroupByName((data[0] as ContactGroup).Name);

            if (group != null) session.SSI.RemoveGroup(group);
         }
         else if ((actions & SaveAccountActions.MoveContact) == SaveAccountActions.MoveContact)
         {
            handled = true;

            if (data.Length < 2) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.MoveContact.ToString());
            if (!(data[1] is ContactGroup)) throw new Exception("Invalid param type for SaveAccountData." + SaveAccountActions.MoveContact.ToString());

            if (_RemoveUserOccured || _MoveUserCount > 0) return;

            Contact contact = (Contact)data[0];
            SSIBuddy buddy = GetBuddyByName(session, contact.Login);
            SSIGroup group = session.SSI.GetGroupByName((data[1] as ContactGroup).Name);

            if (buddy != null && group != null && group.Name != "") session.SSI.MoveBuddy(buddy, group, 0);

            _MoveUserCount++;
         }
         else if ((actions & SaveAccountActions.RenameContactsItem) == SaveAccountActions.RenameContactsItem)
         {
            handled = true;

            if (data.Length < 1) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.RenameContactsItem.ToString());

            if (data[0] is Contact)
            {
               Contact contact = (Contact)data[0];
               SSIBuddy buddy = GetBuddyByName(session, contact.Login);
               if (buddy != null)
               {
                  buddy.DisplayName = contact.CustomName;
                  session.SSI.ModifyBuddy(buddy);
               }
            }
            else if (data[0] is ContactGroup)
            {
               if (data.Length < 2) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.RenameContactsItem.ToString());
               if (!(data[1] is string)) throw new Exception("Invalid param type for SaveAccountData." + SaveAccountActions.RenameContactsItem.ToString());

               SSIGroup group = session.SSI.GetGroupByName((string)data[1]);
               if (group != null) session.SSI.RenameGroup(group.ID, (data[0] as ContactGroup).Name);
            }
         }

         if (!handled)
         {
            OnSaveAccountDataState(this, new SaveAccountDataEventArgs(actions, null, OperationState.None, GetOperationFault(ErrorReasonCode.NotSupported, "", actions.ToString())));
         }
      }
      public override void SaveMediaSession(IMediaSession session, SaveMediaSessionActions actions, params object[] data)
      {
         //Session aolSession = GetAolSession(session.Account, false);

         //if (aolSession == null || !aolSession.LoggedIn)
         //{
         //   OnSaveMediaSessionState(this, new SaveMediaSessionEventArgs(session, actions, null, OperationState.None, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
         //   return;
         //}

         //ChatRoom room = aolSession.ChatRooms.GetChatRoom(session.Caption);

         //if (room == null)
         //{
         //   string nwoName = "{" + session.Gsid + "}.SAVESESSION";

         //   NetworkOperation nwo = CreateOperation(nwoName, true, session.Account, FLongTimeout, null, "Actions", actions, "Data", data);

         //   if (nwo == null)
         //   {
         //      OnSaveMediaSessionState(this, new SaveMediaSessionEventArgs(session, actions, GetOperationByName(nwoName), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready)));
         //   }
         //   else
         //   {
         //      aolSession.ChatRooms.CreateChatRoom(session.Subject, CultureInfo.CurrentUICulture, Encoding.Default);
         //   }
         //}
         //else
         //{
         //   OnSaveMediaSessionState(this, new SaveMediaSessionEventArgs(session, actions, null, OperationState.None, new ServiceOperationException(ErrorReasonCode.NotSupported)));
         //}
      }
      public override void SetPermissions(Account account, object target, PolicyRule message, PolicyRule invitation, PolicyRule visibility)
      {
         Session session = GetAolSession(account, false);

         if (session == null || !session.LoggedIn)
         {
            OnSaveAccountDataState(this, new SaveAccountDataEventArgs(SaveAccountActions.SaveSettings, null, OperationState.Completed, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         if (target is Contact)
         {
            Contact contact = (Contact)target;

            SSIBuddy buddy = GetBuddyByName(session, contact.Login);
            SSIDeny deny = GetDenyItemByName(session, contact.Login);
            SSIPermit permit = GetPermitItemByName(session, contact.Login);

            if (visibility == PolicyRule.Allow)
            {
               if (deny != null) session.SSI.RemoveDenyItem(deny);
               if (permit == null) session.SSI.AddPermitItem(contact.Login);
            }
            else if (visibility == PolicyRule.Block || visibility == PolicyRule.Deny)
            {
               if (permit != null) session.SSI.RemovePermitItem(permit);
               if (deny == null) session.SSI.AddDenyItem(contact.Login);
            }
            else
            {
               if (permit != null) session.SSI.RemovePermitItem(permit);
               if (deny != null) session.SSI.RemoveDenyItem(deny);
            }
         }
         else if (target is string)
         {
            SSIGroup group = session.SSI.GetGroupByName((string)target);

            if (group != null)
            {
               foreach (ushort buddyId in group.Children)
               {
                  SSIBuddy buddy = session.SSI.GetBuddyByID(buddyId, group.ID);

                  if (buddy != null)
                  {
                     SSIDeny deny = GetDenyItemByName(session, buddy.Name);
                     SSIPermit permit = GetPermitItemByName(session, buddy.Name);

                     if (visibility == PolicyRule.Allow)
                     {
                        session.SSI.AddPermitItem(buddy.Name);
                     }
                     else if (visibility == PolicyRule.Block || visibility == PolicyRule.Deny)
                     {
                        session.SSI.AddDenyItem(buddy.Name);
                     }
                     else
                     {
                        if (deny != null) session.SSI.RemoveDenyItem(deny);
                     }
                  }
               }
            }
         }
      }

      public override bool IsConnected(Account account)
      {
         Session session = GetAolSession(account, false);

         return session != null ? session.LoggedIn : false;
      }
      public override void ShowDriverSettings(string configName)
      {
         if (FDriverSettings == null) FDriverSettings = new OscarDriverSettings(this);

         FDriverSettings.Visualize();
      }
      public override ushort AddListener(string name, ushort port)
      {
         throw new NotSupportedException("Add Listener");
      }
      public override IRegisterWidget CreateRegisterWidget(ServiceConfig config)
      {
         throw new NotSupportedException("Registering in AOL");
      }
      public override ServiceConfig CreateConfig(XElement xConfig, ServiceInfo info)
      {
         return new AolServiceConfig(xConfig, info) as ServiceConfig;
      }

      protected override void CreateDefaultConfig()
      {
         FDefaultConfig = FServices[0].Configurations[0];
      }
      protected override string GetDefaultNetworkSettings()
      {
         return DefaultNetworkSettings;
      }

      // HACK: hack of 0x00 simbol and other "bad" simbols because of inaccurate protocol parsing :( 
      public override void OnChatMessageReceived(object sender, MessagingOperationEventArgs e)
      {
         StringBuilder sb = new StringBuilder(e.EventMessage.Text.Length);

         foreach (char c in e.EventMessage.Text) if (TextSvc.IsReadable(c)) sb.Append(c);

         e.EventMessage.Text = sb.ToString();

         if (e.EventMessage.Text.Length > 0) base.OnChatMessageReceived(sender, e);
      }

      #endregion

      #region ICQ Client Members

      private void session_LoginCompleted(Session session)
      {
         NetworkOperation nwo = GetOperationByName(session.ScreenName + ".LOGON");

         if (nwo == null)
         {
            session.Logoff();
            return;
         }

         FAolSessions[nwo.Account] = session;

         OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));

         RemoveOperation(nwo);

         session.Messages.RetrieveOfflineMessages();
      }
      private void session_StatusUpdate(Session session, string message)
      {
         //
      }
      private void session_LoginFailed(Session session, LoginErrorCode reason)
      {
         NetworkOperation nwo = GetOperationByName(session.ScreenName + ".LOGON");

         OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, GetOperationFault(reason)));

         RemoveOperation(nwo);
      }
      private void session_ErrorMessage(Session session, ServerErrorCode error)
      {
         Account account = GetAccountBySession(session);

         session.Logoff();
         Logoff(account);

         NetworkOperation nwo = CreateOperation("SERVER_ERROR", false, account, 0, null);
         
         OnNotifyPresenceState(this, new NetworkOperationEventArgs(nwo, OperationState.Callback, GetOperationFault(error)));
         
         RemoveOperation(nwo);
      }
      private void session_WarningMessage(Session session, ServerErrorCode error)
      {
         //
      }
      private void session_DirectoryUpdateAcknowledged(Session session, bool success)
      {
         NetworkOperation nwo = GetOperationByName(session.ScreenName + ".SAVEACCOUNTDATA");

         if (nwo == null) return;

         if (success)
         {
            OnSaveAccountDataState(this, new SaveAccountDataEventArgs((SaveAccountActions)nwo.Params["Actions"], nwo, OperationState.Completed, null));
         }
         else
         {
            OnSaveAccountDataState(this, new SaveAccountDataEventArgs((SaveAccountActions)nwo.Params["Actions"], nwo, OperationState.Faulted, GetOperationFault(ErrorReasonCode.ServerInternal)));
         }

         RemoveOperation(nwo);
      }
      private void session_LoginStatusUpdate(Session session, string message, double percentdone)
      {
         NetworkOperation nwo = GetOperationByName(session.ScreenName + ".LOGON");

         if (nwo == null)
         {
            session.Logoff();
            return;
         }

         if (percentdone < 1.0) OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Callback, null));

         if (percentdone == 0.66) OnSyncContactsState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSending, null));
      }

      private void session_SSIEditComplete(Session session)
      {
         //
      }
      private void session_ContactListFinished(Session session, DateTime dt)
      {
         NetworkOperation nwo = GetOperationByName(session.ScreenName + ".LOGON");

         if (nwo == null) return;

         XmlDocument xmlContacts = (XmlDocument)nwo.Params["XContacts"];

         Dictionary<string, XmlElement> treeStruct = new Dictionary<string, XmlElement>();

         foreach (XmlElement xGroup in xmlContacts.DocumentElement.ChildNodes)
         {
            foreach (XmlElement xContact in xGroup.ChildNodes)
            {
               string login = xContact.GetAttribute("Login");
               SSIDeny deny = GetDenyItemByName(session, login);
               SSIPermit permit = GetPermitItemByName(session, login);

               XmlElement xSettings = null;

               if (permit != null)
               {
                  xSettings = xmlContacts.CreateElement("Settings");
                  xSettings.SetAttribute("VisibilityRule", PolicyRule.Allow.ToString());
               }
               if (deny != null)
               {
                  if (xSettings == null) xSettings = xmlContacts.CreateElement("Settings");
                  xSettings.SetAttribute("VisibilityRule", PolicyRule.Block.ToString());
               }

               if (xSettings != null) xContact.AppendChild(xSettings);
            }
         }
         foreach (XmlElement xElement in xmlContacts.DocumentElement.ChildNodes)
         {
            if (xElement.Name == "Group" && xElement.GetAttribute("Name").StartsWith(">") && xElement.PreviousSibling != null)
            {
               treeStruct.Add(xElement.PreviousSibling.Attributes.GetNamedItem("Id").Value, xElement);
            }
         }
         foreach (KeyValuePair<string, XmlElement> xe in treeStruct)
         {
            xmlContacts.GetElementById(xe.Key).AppendChild(xe.Value);
            xe.Value.SetAttribute("Name", xe.Value.GetAttribute("Name").Remove(0, 1));
         }

         nwo.Results["Version"] = nwo.Account.ContactsVersion + 1;
         nwo.Results["XContacts"] = xmlContacts.DocumentElement.OuterXml;

         session.SSI.Privacy = PrivacySetting.BlockAllUsers;

         Thread.Sleep(500);

         session.ActivateBuddyList(); // must be earlier than OnSyncContactsState

         OnSyncContactsState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));
      }
      private void session_GroupItemReceived(Session session, SSIGroup group)
      {
         NetworkOperation nwo = GetOperationByName(session.ScreenName + ".LOGON");

         if (nwo == null) return;

         XmlDocument xmlContacts = (XmlDocument)nwo.Params["XContacts"];

         XmlElement el = xmlContacts.CreateElement("Group");

         el.SetAttribute("Id", group.ID.ToString());
         el.SetAttribute("Name", group.Name);

         xmlContacts.DocumentElement.AppendChild(el);
      }
      private void session_BuddyItemReceived(Session session, SSIBuddy buddy)
      {
         NetworkOperation nwo = GetOperationByName(session.ScreenName + ".LOGON");

         if (nwo == null) return;

         XmlDocument xmlContacts = (XmlDocument)nwo.Params["XContacts"];

         XmlElement xContact = xmlContacts.CreateElement("Contact");

         string displayName = buddy.DisplayName;
         if (buddy.DisplayName.Length > 15) displayName = buddy.DisplayName.SubstrBeforeLast(" ");
         if (buddy.DisplayName.Length > 15) displayName = buddy.DisplayName.SubstrBefore(" ");
         if (buddy.DisplayName.Length > 15) displayName = buddy.DisplayName.Substr(0, 15);

         xContact.SetAttribute("Login", buddy.Name);
         xContact.SetAttribute("CustomName", displayName);

         XmlElement xGroup = xmlContacts.GetElementById(buddy.GroupID.ToString());

         xGroup.AppendChild(xContact);
      }

      private void session_UserStatusReceived(object sender, csammisrun.OscarLib.UserInfo info)
      {
         Session session = GetSessionByManager((ISnacFamilyHandler)sender);
         Account account = GetAccountBySession(session);
         StatusCodes status = TypeMap.MarshalStatus(info, IsIcqAccount(account));
         
         string text = info.AvailableMessage;

         if (text == null) text = "";

         PresenceNotification pn = new PresenceNotification(status, text, new User(info.ScreenName),
                                                            account, account.ServiceConfig.Service);
         OnPresenceNotified(this, new MessagingOperationEventArgs(pn, null, OperationState.None, null));

         //session.Statuses.RequestBasicUserInfo(info.ScreenName, Status.IsAway(status) ?
         //                                      BasicUserInfoRequest.AwayMessage : BasicUserInfoRequest.GeneralInfo);
         //FBuddyCache[info.ScreenName] = info;
         //if (info.Icon != null) session.Graphics.DownloadBuddyIcon(info.ScreenName, info.Icon);
      }

      private void session_DirectIMReceived(Session session, DirectIM msg)
      {
         Account account = GetAccountBySession(session);

         OnChatMessageReceived(this, new MessagingOperationEventArgs(
            new ChatMessage(msg.Message, new User(msg.ScreenName), new User(session.ScreenName), account.ServiceConfig.Service), null, OperationState.None, null));
      }
      private void session_MessageReceived(object sender, MessageReceivedEventArgs e)
      {
         Session session = GetSessionByManager((ISnacFamilyHandler)sender);
         Account account = GetAccountBySession(session);

         OnChatMessageReceived(this, new MessagingOperationEventArgs(
            new ChatMessage(e.Message.Message, new User(e.Message.ScreenName), new User(session.ScreenName), account.ServiceConfig.Service), null, OperationState.None, null));
      }
      private void session_MessageDeliveryUpdate(object sender, MessageStatusEventArgs e)
      {
         Session session = GetSessionByManager((ISnacFamilyHandler)sender);
         Account account = GetAccountBySession(session);

         NetworkOperation nwo = GetOperationByName(session.ScreenName + "->" + e.Destination + ".CHATMESSAGE");

         ChatMessage request = (ChatMessage)nwo.Params["Message"];

         ServiceMessage ack = new ServiceMessage(ServiceActions.MSGACK, account.ServiceConfig.Service, 0,
                                                 new User(IsIcqAccount(account) ? "ICQ System ICQ:12111" : "AOL System AIM:system"),
                                                 account, (int)e.Status, "");
         ack.TsLocal = DateTime.UtcNow;
         ack.TsServerOut = e.Timestamp;

         OnAcknowledgeReceived(this, new MessagingOperationEventArgs(request, ack, nwo, OperationState.Completed,
                               e.Status == MessageStatus.AcceptedForDelivery ? null : GetOperationFault(e.Status)));
         RemoveOperation(nwo);
      }
      private void session_TypingNotification(object sender, TypingNotificationEventArgs e)
      {
         Session session = GetSessionByManager((ISnacFamilyHandler)sender);
         Account account = GetAccountBySession(session);

         OnChatMessageReceived(this, new MessagingOperationEventArgs(
                               new CoreTypes.TypingNotification(false,
                               e.Notification == csammisrun.OscarLib.TypingNotification.TextTyped ||
                               e.Notification == csammisrun.OscarLib.TypingNotification.TypingFinished,
                               new User(e.ScreenName), new User(session.ScreenName), account.ServiceConfig.Service),
                               null, OperationState.None, null));
      }
      private void session_OfflineMessagesReceived(object sender, OfflineMessagesReceivedEventArgs e)
      {
         Thread.Sleep(10000);

         Session session = GetSessionByManager((ISnacFamilyHandler)sender);
         Account account = GetAccountBySession(session);

         foreach (OfflineIM msg in e.ReceivedMessages)
         {
            ChatMessage message = new ChatMessage(msg.Message, new User(msg.ScreenName), new User(session.ScreenName), account.ServiceConfig.Service);
            
            message.TsServerIn = msg.ReceivedOn;

            OnChatMessageReceived(this, new MessagingOperationEventArgs(message, null, OperationState.None, null));
         }
      }
      private void session_BadMessageReceived(object sender, MessageManager.BadMessageReceivedEventArgs e)
      {
         Session session = GetSessionByManager((ISnacFamilyHandler)sender);
         Account account = GetAccountBySession(session);

         string text = "";
         e.DataPacket.Data.AdvanceToPosition(84);
         int length = e.DataPacket.Data.GetRemainingByteCount() - 14;
         byte[] buffer = e.DataPacket.Data.ReadByteArray(length);

         try { text = Encoding.Default.GetString(buffer); } catch { }

         ChatMessage message = new ChatMessage(text, new User(e.Message.ScreenName), account, account.ServiceConfig.Service);

         OnChatMessageReceived(this, new MessagingOperationEventArgs(message, null, OperationState.None, null));
      }
      private void session_DirectIMRequestReceived(Session session, csammisrun.OscarLib.UserInfo sender, string message, csammisrun.OscarLib.Cookie key)
      {
         //session.AcceptDirectIMSession(key);
      }

      private void session_FutureAuthorizationReceived(Session session, string screenname, string reason)
      {
         session_AuthorizationResponseReceived(session, screenname, true, reason);
      }
      private void session_AuthorizationRequestReceived(Session session, string screenname, string reason)
      {
         Account account = GetAccountBySession(session);
         SSIBuddy buddy = GetBuddyByName(session, screenname);
         User sender = new User(screenname, buddy != null ? buddy.DisplayName : screenname);

         if (buddy != null) buddy.AwaitingAuthorization = true;

         ChatMessage message = new ChatMessage(ServiceActions.AUTHREQ, "" + reason, sender, account, account.ServiceConfig.Service, string.IsNullOrEmpty(reason)); // IsSystem == true -> will not be checked by anti-spam filter

         MessagingOperationEventArgs args = new MessagingOperationEventArgs(message, null, OperationState.None, null);

         OnChatMessageReceived(this, args);

         if (args.AppliedRule == PolicyRule.Allow) session.SendAuthorizationResponse(screenname, true, "You was added to my contacts.");
      }
      private void session_AuthorizationResponseReceived(Session session, string screenname, bool authorizationGranted, string reason)
      {
         Account account = GetAccountBySession(session);
         SSIBuddy buddy = GetBuddyByName(session, screenname);

         buddy.AwaitingAuthorization = !authorizationGranted;

         if (string.IsNullOrEmpty(reason)) reason = authorizationGranted ? "You was added to my contacts." : "Sorry, I can't add you to my contacts, may be later.";

         OnChatMessageReceived(this, new MessagingOperationEventArgs(new ChatMessage(ServiceActions.AUTHRESP, reason,
                               new User(screenname), account, account.ServiceConfig.Service, true), null, OperationState.None, null));
      }

      private void session_UserInfoReceived(object sender, UserInfoResponse info)
      {
         Session session = GetSessionByManager((ISnacFamilyHandler)sender);
         Account account = GetAccountBySession(session);
         StatusCodes status = TypeMap.MarshalStatus(info.Info, IsIcqAccount(account));

         string message = Status.IsAway(status) ? info.AwayMessage : info.Info.AvailableMessage;

         if (message == null) message = "";

         PresenceNotification pn = new PresenceNotification(status, message, new User(info.Info.ScreenName),
                                       account, account.ServiceConfig.Service);

         OnPresenceNotified(this, new MessagingOperationEventArgs(pn, null, OperationState.None, null));
      }
      private void session_FindByEmailResults(object sender, FindByEmailResultsArgs e)
      {
         Session session = GetSessionByManager((ISnacFamilyHandler)sender);
         Account account = GetAccountBySession(session);
         NetworkOperation nwo = GetOperationByName(account.Login + ".FINDBYEMAIL");

         if (nwo != null)
         {
            List<User> users = new List<User>();
            foreach (string name in e.Results)
            {
               User user = new User(name);
               user.Info.Email = e.Email;
               users.Add(user);
            }
            nwo.Results["Users"] = users;
            OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));
            RemoveOperation(nwo);
         }
      }
      private void session_SearchResultReceived(object sender, SearchResultReceivedEventArgs e)
      {
         NetworkOperation nwo = GetOperationByName(e.DirectoryEntry.ScreenName + ".REQUESTUSERINFO");

         if (nwo != null)
         {
            User user = (User)nwo.Params["User"];

            if (e.DirectoryEntry.NickName != "") user.Nickname = e.DirectoryEntry.NickName;
            if (e.DirectoryEntry.FirstName != "") user.Info.FullName = e.DirectoryEntry.FirstName + " " + e.DirectoryEntry.LastName;

            nwo.Results["User"] = user;

            OnRequestUserInfoState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));

            RemoveOperation(nwo);
         }
      }
      private void session_UserInfoResponseReceived(object sender, SearchResultReceivedEventArgs e)
      {
         NetworkOperation nwo = GetOperationByName("REQUESTUSERINFO");

         if (nwo != null)
         {
            bool isSearch = nwo.Params.ContainsKey("IsSearch");

            if (e.Error != null)
            {
               RemoveOperation(nwo);
               if (isSearch) OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, GetOperationFault(e.Error)));
               else OnRequestUserInfoState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, GetOperationFault(e.Error)));
            }
            else if (e.IsEmpty == false)
            {
               int resultsCount = 0;

               lock (nwo)
               {
                  User user = (User)nwo.Params["User"];

                  resultsCount = (int)nwo.Results["Count"];
                  nwo.Results["Count"] = ++resultsCount;

                  if (e.DirectoryEntry.NickName != "") user.Nickname = e.DirectoryEntry.NickName;
                  if (e.DirectoryEntry.FirstName != "") user.Info.FullName = e.DirectoryEntry.FirstName + " " + e.DirectoryEntry.LastName;
                  if (e.DirectoryEntry.Email != "") user.Info.Email = e.DirectoryEntry.Email;
                  if (e.DirectoryEntry.Country != "") user.Info.Country = e.DirectoryEntry.Country;
                  if (e.DirectoryEntry.City != "") user.Info.City = e.DirectoryEntry.City;

                  if (isSearch) nwo.Results["Users"] = new User[] { user };
                  else nwo.Results["User"] = user;
               }

               if (resultsCount == SearchResultReceivedEventArgs.InfoResponseTypeHandledCount)
               {
                  if (isSearch) OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));
                  else OnRequestUserInfoState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));
                  RemoveOperation(nwo);
               }
            }
            else
            {
               if (isSearch)
               {
                  nwo.Results["Users"] = new User[] { };
                  OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));
               }
               else
               {
                  nwo.Results["User"] = null;
                  OnRequestUserInfoState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));
               }
               RemoveOperation(nwo);
            }
         }
      }

      private void graphics_BuddyIconReceived(object sender, BuddyIconReceivedEventArgs e)
      {
         //
      }
      private void graphics_BuddyIconUploadFailed(object sender, BuddyIconUploadFailedArgs e)
      {
         //
      }
      private void graphics_BuddyIconDownloaded(object sender, BuddyIconDownloadedEventArgs e)
      {
         //
      }
      private void graphics_BuddyIconUploadCompleted(object sender, BuddyIconUploadCompletedArgs e)
      {
         //
      }

      private void session_FileTransferCompleted(Session session, string filename)
      {
         //
      }
      private void session_FileTransferProgress(Session session, string filename, uint BytesTransfered, uint BytesTotal)
      {
         //
      }
      private void session_FileTransferRequestReceived(Session session, csammisrun.OscarLib.UserInfo sender, string IP, string filename, uint filesize, string key)
      {
         //
      }

      private void session_ChatRoomCreated(object sender, ChatRoom room)
      {
         room.UserLeft += new ChatRoomChangedHandler(chatRoom_UserLeft);
         room.UserJoined += new ChatRoomChangedHandler(chatRoom_UserJoined);
         room.MessageReceived += new MessageReceivedHandler(chatRoom_MessageReceived);
      }
      private void session_ChatRoomCreationFailed(object sender, ChatRoom newroom)
      {
         //
      }
      private void chatRoom_UserLeft(object sender, ChatRoomChangedEventArgs e)
      {
         ChatRoom room = sender as ChatRoom;
      }
      private void chatRoom_UserJoined(object sender, ChatRoomChangedEventArgs e)
      {
         ChatRoom room = sender as ChatRoom;
      }
      private void chatRoom_MessageReceived(object sender, MessageReceivedEventArgs e)
      {
         ChatRoom room = sender as ChatRoom;
      }
      private void session_ChatInvitationReceived(Session session, csammisrun.OscarLib.UserInfo sender, string roomname, string message,
                                               System.Text.Encoding encoding, string language, csammisrun.OscarLib.Cookie key)
      {
         //session.ChatRooms.JoinChatRoom(key);
      }

      #endregion
   }

   internal static class TypeMap
   {
      internal static StatusCodes MarshalStatus(csammisrun.OscarLib.UserInfo info, bool icq)
      {
         if (info.OnlineTime == 0xFFFFFFFF)
         {
            return StatusCodes.Offline;
         }
         else
         {
            switch ((ushort)info.ICQUserStatus)
            {
               case 0x0020: return StatusCodes.Online; // FreeForChat
               case 0x0100: return StatusCodes.Invisible;
               case 0x0001: return StatusCodes.SeemsAway;
               case 0x0004: return StatusCodes.NotAvailable;
               case 0x0005: return StatusCodes.NotAvailable;
               case 0x0002: return StatusCodes.NotAvailable; // DND
               case 0x0013: return StatusCodes.NotAvailable; // DND
               case 0x0010: return StatusCodes.NotAvailable; // Occupied
               case 0x0011: return StatusCodes.NotAvailable; // Occupied
               default: return StatusCodes.Online;
            }
         }
      }

      internal static ICQStatus MarshalStatus(StatusCodes status)
      {
         switch (status)
         {
            case StatusCodes.SeemsAway: return ICQStatus.Away;
            case StatusCodes.Phantom: return ICQStatus.Invisible;
            case StatusCodes.Invisible: return ICQStatus.Invisible;
            case StatusCodes.NotAvailable: return ICQStatus.NotAvailable;
            default: return ICQStatus.Online;
         }
      }
   }

   internal class AolServiceConfig : ServiceConfig
   {
      protected override void SetDefaultCapabilities()
      {
         if (FCapabilities == null) FCapabilities = new Dictionary<string, string>(); else FCapabilities.Clear();

         FCapabilities.Add("Features", StringExt.Join(new ServiceFeatures[]
         {
            ServiceFeatures.AllowList,
            ServiceFeatures.BlockList,
            ServiceFeatures.AddContact,
            ServiceFeatures.FindPerson,

         }.Select(f => f.ToString()), " "));

         FCapabilities.Add("SearchBy", StringExt.Join(new ServiceFields[]
         {
            ServiceFields.Id,

         }.Select(f => f.ToString()), " "));

         FCapabilities.Add("GettingOf", StringExt.Join(new ServiceFields[]
         {
            ServiceFields.Nickname, ServiceFields.FullName, ServiceFields.FirstName, ServiceFields.LastName,
            ServiceFields.Email, ServiceFields.Country, ServiceFields.City, 

         }.Select(f => f.ToString()), " "));

         //FCapabilities.Add("SettingOf", StringExtension.ConcatArray(new ServiceFields[]
         //{
         //   ServiceFields.Nickname, ServiceFields.FirstName, ServiceFields.LastName,
         //   ServiceFields.Email, ServiceFields.Country, ServiceFields.City, 

         //}.Select(f => f.ToString()), " "));
      }

      internal AolServiceConfig(ServiceInfo info)
         : base(info)
      {
         FPort = CommunicationServiceObject.AolLoginServerPort;
         FHost = CommunicationServiceObject.AolLoginServerHost;

         SetDefaultCapabilities();
      }

      internal AolServiceConfig(XElement xConfig, ServiceInfo info)
         : base(xConfig, info)
      {
         if (FPort == 0) FPort = CommunicationServiceObject.AolLoginServerPort;
         if (string.IsNullOrEmpty(FHost)) FHost = CommunicationServiceObject.AolLoginServerHost;

         if (FCapabilities == null) SetDefaultCapabilities();
      }

      public override XElement XObject
      {
         get
         {
            XElement x = base.XObject;

            if (FPort == CommunicationServiceObject.AolLoginServerPort) x.SetAttributeValue("Port", null);
            if (FHost == CommunicationServiceObject.AolLoginServerHost) x.SetAttributeValue("Host", null);

            return x;
         }
      }
   }

   internal class NotSupportedException : ServiceOperationException
   {
      public static string GetNotSupportedMessage(string feature)
      {
         return "This feature (" + feature + ") is not supported by the currently installed AOL driver.";
      }

      public NotSupportedException(string feature)
         : base(ErrorReasonCode.NotSupported, GetNotSupportedMessage(feature))
      {
      }
   }
}